using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Win32;

namespace Code5Systems.ClientFramework
{
	#region ISymmetricCryptoProvider
	internal interface ISymmetricCryptoProvider
	{
		byte[] Encrypt(byte[] plaintext);
		byte[] Decrypt(byte[] cipherText);
	}

	internal interface ISymmetricCryptoKeyProvider
	{
		string GetSymmetricKey();
	} 
	#endregion

	#region RijndaelCryptoHelper
	internal class RijndaelCryptoHelper : ISymmetricCryptoProvider
	{
		#region Fields
		private byte[] m_Key = null;

		//private const string m_ConfigRegistryValue = "Symmetric key";
		private RegistryKey m_registryKey = null;

		private ISymmetricCryptoKeyProvider m_KeyPorovider = null;
		#endregion

		#region Constructor
		public RijndaelCryptoHelper() { }
		#endregion

		#region Public methods
		public byte[] Encrypt(byte[] pValue)
		{
			byte[] chiperValue = { };

			byte[] key = GetKey();

			SymmetricCryptographer algorithm = new SymmetricCryptographer(typeof(RijndaelManaged).ToString(), key, PaddingMode.PKCS7);

			try
			{
				chiperValue = algorithm.Encrypt(pValue);
			}
			catch (CryptographicException exc)
			{
				throw new CryptographicException(exc.Message);
			}
			finally
			{
				key = null;
				this.m_Key = null;
			}

			return chiperValue;
		}

		public byte[] Decrypt(byte[] cipherValue)
		{
			byte[] plainValue = new byte[cipherValue.Length];

			byte[] key = GetKey();
			SymmetricCryptographer algorithm = new SymmetricCryptographer(typeof(RijndaelManaged).ToString(), key, PaddingMode.PKCS7);
			try
			{
				plainValue = algorithm.Decrypt(cipherValue);
			}
			catch (CryptographicException exc)
			{
				throw new CryptographicException(exc.Message);
			}
			finally
			{
				key = null;
				this.m_Key = null;
			}

			return plainValue;
		}

		public string Encrypt(string pValue)
		{
			return Convert.ToBase64String(this.Encrypt(Encoding.Unicode.GetBytes(pValue)));
		}

		public string Decrypt(string cipherValue)
		{
			return Encoding.Unicode.GetString(this.Decrypt(Convert.FromBase64String(cipherValue)));
		}

		public string Encrypt(string pKey, string pValue)
		{
			this.m_Key = Encoding.Unicode.GetBytes(pKey);
			return Convert.ToBase64String(this.Encrypt(Encoding.Unicode.GetBytes(pValue)));
		}

		public string Decrypt(string pKey, string pValue)
		{
			this.m_Key = Encoding.Unicode.GetBytes(pKey);
			return Encoding.Unicode.GetString(this.Decrypt(Convert.FromBase64String(pValue)));
		}

		#endregion

		#region Symmetric key declaration

		private byte[] GetKey()
		{
			byte[] key;
			string sKey;

			if (m_registryKey == null)
			{
				if (this.m_Key != null)
					key = this.m_Key;
				else
				{
					if (this.m_KeyPorovider != null) sKey = this.m_KeyPorovider.GetSymmetricKey();
					else
						sKey = "B0B7#967E#4f94#9305#A6F9";

					key = Encoding.ASCII.GetBytes(sKey);
				}
				sKey = String.Empty;
			}
			else
			{
				string regKeyVALUE = m_registryKey.GetValue("Symmetric key").ToString();
				key = Encoding.ASCII.GetBytes(regKeyVALUE);
				regKeyVALUE = null;
			}
			return key;
		}

		#endregion


	} 
	#endregion

	#region SymmetricCryptographer
	internal class SymmetricCryptographer
	{
		private SymmetricAlgorithm algorithm;
		private byte[] key;

		public SymmetricCryptographer(SymmetricAlgorithm algorithm, byte[] key)
		{
			this.algorithm = algorithm;
			this.key = key;
		}

		public SymmetricCryptographer(SymmetricAlgorithm algorithm, byte[] key, PaddingMode paddingMode)
			: this(algorithm, key)
		{
			this.algorithm.Padding = paddingMode;
		}

		public SymmetricCryptographer(string algorithmType, byte[] key)
		{
			this.key = key;
			this.algorithm = GetSymmetricAlgorithm(algorithmType);
		}

		public SymmetricCryptographer(string algorithmType, byte[] key, PaddingMode paddingMode)
			: this(algorithmType, key)
		{
			this.algorithm.Padding = paddingMode;
		}

		public byte[] Encrypt(byte[] plaintext)
		{
			byte[] output = null;
			byte[] cipherText = null;

			this.algorithm.Key = key;

			using (ICryptoTransform transform = this.algorithm.CreateEncryptor())
			{
				cipherText = Transform(transform, plaintext);
			}

			output = new byte[IVLength + cipherText.Length];
			Buffer.BlockCopy(this.algorithm.IV, 0, output, 0, IVLength);
			Buffer.BlockCopy(cipherText, 0, output, IVLength, cipherText.Length);

			return output;
		}

		public byte[] Decrypt(byte[] encryptedText)
		{
			byte[] output = null;
			byte[] data = ExtractIV(encryptedText);

			using (ICryptoTransform transform = this.algorithm.CreateDecryptor())
			{
				output = Transform(transform, data);
			}

			return output;
		}

		private static byte[] Transform(ICryptoTransform transform, byte[] buffer)
		{
			byte[] transformBuffer = null;

			using (MemoryStream ms = new MemoryStream())
			{
				CryptoStream cs = null;
				try
				{
					cs = new CryptoStream(ms, transform, CryptoStreamMode.Write);
					cs.Write(buffer, 0, buffer.Length);
					cs.FlushFinalBlock();
					transformBuffer = ms.ToArray();
				}
				finally
				{
					if (cs != null)
					{
						cs.Close();
						((IDisposable)cs).Dispose();
					}
				}
			}

			return transformBuffer;
		}

		private int IVLength
		{
			get
			{
				if (this.algorithm.IV == null)
				{
					this.algorithm.GenerateIV();
				}
				return this.algorithm.IV.Length;
			}
		}

		private byte[] ExtractIV(byte[] encryptedText)
		{
			byte[] initVector = new byte[IVLength];

			if (encryptedText.Length < IVLength + 1)
			{
				throw new CryptographicException(Resources.StringLibrary.UnableDecryptData);
			}

			byte[] data = new byte[encryptedText.Length - IVLength];

			Buffer.BlockCopy(encryptedText, 0, initVector, 0, IVLength);
			Buffer.BlockCopy(encryptedText, IVLength, data, 0, data.Length);

			this.algorithm.IV = initVector;
			this.algorithm.Key = this.key;

			return data;
		}

		private static SymmetricAlgorithm GetSymmetricAlgorithm(string algorithmType)
		{
			SymmetricAlgorithm sa = null;
			try
			{
				Type type = Type.GetType(algorithmType, true);
				sa = Activator.CreateInstance(type) as SymmetricAlgorithm;
			}
			catch (Exception ex)
			{
				throw new CryptographicException(Resources.StringLibrary.UnableLoadAlgorithm, ex);
			}

			if (sa == null)
			{
				throw new CryptographicException(Resources.StringLibrary.TypeMustInheritFromSymmetricAlgorithm);
			}

			return sa;
		}
	} 
	#endregion
}
